// HSS.Forms.DataListView.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       DataListView.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/23/2010
// ----------------------------------------------------------------------------
namespace HSS.Forms
{
	#region Using Directives
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Drawing;
	using System.Linq;
	using System.Text;
	using System.Windows.Forms;
	using HSS.Data;
	using System.Diagnostics;
	#endregion

	#region DataListView
	/// <summary>
	/// A data list view displays values from an <see cref="IEnumerable"/> list of data objects
	/// </summary>
	[Description("A data list view displays values from an list of data objects")]
	[ToolboxBitmap(typeof(HSS.Forms.DataListView), "HSS.Forms.DataListView")]
	[DefaultProperty("ViewName")]
	public sealed class DataListView : ListView, ISupportInitialize, ICanBeDirty, IClipboardCapable
	{
		#region Constants
		private const int WM_PAINT = 15;
		private const int WM_ERASEBKGND = 0x14;
		private const string DEFAULT_VIEWNAME = "DataListView";
		private const string SORT_INDICATOR_UP = "sort-up";
		private const string SORT_INDICATOR_DOWN = "sort-down";
		/// <summary>
		/// The string used to separate sections of Xml on the clipboard
		/// </summary>
		public const string XmlClipSeparator = "__!$-$!__";
		#endregion

		#region Fields
		ViewManager viewMgr;
		int noItemsTop = 35;
		int noItemsWidth = -1;
		int gutter = 40;
		int _previouslySortedColumn = -1;
		internal Dictionary<string, IDataContainer> dataSource;
		internal List<DataListItem> dataListItems;
		List<DataListColumn> detailViewColumns = new List<DataListColumn>();
		List<DataListColumn> tileViewColumns = new List<DataListColumn>();
		#endregion

		#region Events

		#region Dirty
		/// <summary>
		/// Occurs when the underlying data has been modified
		/// </summary>
		[Category("Action")]
		[Description("Occurs when the underlying data has been modified")]
		public event EventHandler<DirtyEventArgs> DirtyChanged;
		/// <summary>
		/// InvokeDirty Event on Clients
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void InvokeDirty(object sender, DirtyEventArgs e)
		{
			if (null != this.DirtyChanged)
			{
				System.Delegate[] dlgs = this.DirtyChanged.GetInvocationList();
				if (null != dlgs)
				{
					foreach (Delegate d in dlgs)
					{
						if (null != d)
						{
							InvokeDelegate(d, new object[2] { sender, e });
						}
					}
				}
			}
		}
		#endregion

		#region DeleteSelectedItem
		/// <summary>
		/// Occurs when the user clicks the delete key and one item is selected
		/// </summary>
		[Category("Action")]
		[Description("Occurs when the user clicks the delete key and a single item is selected")]
		public event EventHandler<ItemDeleteEventArgs> DeleteSelectedItem;
		/// <summary>
		/// Raises the <see cref="DeleteSelectedItem"></see> event.
		/// </summary>
		/// <param name="e">A <see cref="ItemDeleteEventArgs"></see> that contains the event data. </param>
		private void OnDeleteSelectedItem(ItemDeleteEventArgs e)
		{
			if (null != DeleteSelectedItem)
				this.DeleteSelectedItem(this, e);
		}
		#endregion

		#region DeleteSelectedItems
		/// <summary>
		/// Occurs when the user clicks the delete key and multiple items are selected
		/// </summary>
		[Category("Action")]
		[Description("Occurs when the user clicks the delete key and multiple items are selected")]
		public event EventHandler<EventArgs> DeleteSelectedItems;
		/// <summary>
		/// Raises the <see cref="DeleteSelectedItems"></see> event.
		/// </summary>
		private void OnDeleteSelectedItems()
		{
			if (null != DeleteSelectedItems)
				this.DeleteSelectedItems(this, EventArgs.Empty);
		}
		#endregion

		/// <summary>
		/// Properly invokes a delegate allowing for a WinForm thread
		/// </summary>
		/// <param name="del">The delegate to call</param>
		/// <param name="args">Any arguments to be passed</param>
		public static void InvokeDelegate(Delegate del, object[] args)
		{
			System.ComponentModel.ISynchronizeInvoke synchronizer;
			synchronizer = del.Target as System.ComponentModel.ISynchronizeInvoke;
			if (synchronizer != null) //A Windows Forms object
			{
				if (synchronizer.InvokeRequired == false)
				{
					del.DynamicInvoke(args);
					return;
				}
				try
				{
					synchronizer.Invoke(del, args);
				}
				catch
				{ }
			}
			else //Not a Windows Forms object
			{
				del.DynamicInvoke(args);
			}
		}

		#endregion

		#region Constructor
		/// <summary>
		/// ctor
		/// </summary>
		public DataListView()
			: base()
		{
			this.DoubleBuffered = true;
			this.AlternateRowBackColor = Color.Empty;
		}
		#endregion

		#region Properties

		#region Behavior

		/// <summary>
		/// Indicates whether or not to allow items to be cut
		/// </summary>
		[Category("Behavior")]
		[Description("Indicates whether or not to allow items to be cut")]
		[DefaultValue(true)]
		public bool AllowCut
		{
			get { return _allowItemDelete; }
			set { _allowItemDelete = value; }
		}

		/// <summary>
		/// Indicates whether or not to allow items to be cut
		/// </summary>
		[Category("Behavior")]
		[Description("Indicates whether or not to allow items to be deleted")]
		[DefaultValue(true)]
		public bool AllowDelete
		{
			get { return _allowItemDelete; }
			set { _allowItemDelete = value; }
		} bool _allowItemDelete = true;

		/// <summary>
		/// Indicates whether or not to allow items to be copied
		/// </summary>
		[Category("Behavior")]
		[Description("Indicates whether or not to allow items to be copied")]
		[DefaultValue(true)]
		public bool AllowCopy
		{
			get { return _allowItemCopy; }
			set { _allowItemCopy = value; }
		} bool _allowItemCopy = true;

		/// <summary>
		/// Indicates whether or not to allow items to be pasted
		/// </summary>
		[Category("Behavior")]
		[Description("Indicates whether or not to allow items to be pasted")]
		[DefaultValue(true)]
		public bool AllowPaste
		{
			get { return _allowItemPaste; }
			set { _allowItemPaste = value; }
		} bool _allowItemPaste = true;

		/// <summary>
		/// Indicates whether or not to allow items to be pasted
		/// </summary>
		[Category("Behavior")]
		[Description("Indicates whether or not to allow items to be edited")]
		[DefaultValue(true)]
		public bool AllowEdit
		{
			get { return _allowItemEdit; }
			set { _allowItemEdit = value; }
		} bool _allowItemEdit = true;

		/// <summary>
		/// Indicates whether or not to allow all items to be selected
		/// </summary>
		[Category("Behavior")]
		[Description("Indicates whether or not to allow all items to be selected")]
		[DefaultValue(true)]
		public bool AllowSelectAll
		{
			get { return _allowSelectAll; }
			set { _allowSelectAll = value; }
		} bool _allowSelectAll = true;

		/// <summary>
		/// When grouping items, indicates to sort the items within the group itself
		/// </summary> 
		[Category("Behavior")]
		[Description("When grouping items, indicates to sort the items within the group itself")]
		[DefaultValue(true)]
		public bool SortGroupItems
		{
			get { return _sortGroupItems; }
			set { _sortGroupItems = value; }
		} bool _sortGroupItems = true;

		/// <summary>
		/// When grouping items, indicates the default column used to sort the items within the group itself.
		/// </summary> 
		[Category("Behavior")]
		[Description("When grouping items, indicates the default column used to sort the items within the group itself.")]
		[DefaultValue(-1)]
		public int SortGroupItemsColumn
		{
			get { return _sortGroupItemsColumn; }
			set { _sortGroupItemsColumn = value; }
		} int _sortGroupItemsColumn = -1;

		/// <summary>
		/// When loading or refreshing the data in the list, use the beginupdate/endupdate feature
		/// </summary> 
		[Category("Behavior")]
		[Description("When loading or refreshing the data in the list, use the beginupdate/endupdate feature")]
		[DefaultValue(true)]
		public bool UseBeginEndUpdate
		{
			get { return _useBeginEndUpdate; }
			set { _useBeginEndUpdate = value; }
		} bool _useBeginEndUpdate = true;

		/// <summary>
		/// This property controls whether group labels will be suffixed with a count of items.
		/// </summary>
		/// <remarks>
		/// The format of the suffix is controlled by GroupItemCountFormat property
		/// </remarks>
		[Category("Behavior")]
		[Description("Will group titles be suffixed with a count of the items in the group?")]
		[DefaultValue(false)]
		public bool ShowItemCountOnGroups
		{
			get { return _showItemCountOnGroups; }
			set { _showItemCountOnGroups = value; }
		} bool _showItemCountOnGroups = false;

		/// <summary>
		/// Should the list view automatically size it's column?
		/// </summary>
		[Category("Behavior")]
		[Description("Should the list view automatically size it's column?")]
		[DefaultValue(true)]
		public bool AutoSizeColumns
		{
			get { return _autoSizeColumns; }
			set
			{
				if (this._autoSizeColumns != value)
				{
					this._autoSizeColumns = value;
					if (null != this._settings && this._settings.AutoSizeColumns != value)
					{
						this._settings.AutoSizeColumns = value;
						if (this._settings.ShouldPersist)
							viewMgr.Persist(this._settings);
					}
				}
			}
		} bool _autoSizeColumns = true;

		/// <summary>
		/// Gets or set the Filter
		/// </summary>
		[Category("Behavior")]
		[Description("Should the list view automatically size it's column?")]
		[DefaultValue(true)]
		public string Filter
		{
			get { return _filter; }
			set
			{
				if (value == null)
					value = string.Empty;
				if (!_filter.Equals(value))
				{
					_filter = value;
					this.ApplyFilter();
				}
			}
		} string _filter = string.Empty;

		/// <summary>
		/// Gets if this DataListView is currently in FilterMode
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		[EditorBrowsable(EditorBrowsableState.Advanced)]
		public bool IsFiltered
		{
			get;
			private set;
		}

		#endregion

		#region Appearance

		/// <summary>
		/// The maximum width in which the auto column width adjustment is made
		/// </summary> 
		[Category("Appearance")]
		[Description("The maximum width in which the Auto-ColumnWidth-Adjustment will consider")]
		[DefaultValue(500)]
		public int MaxColumnWidth
		{
			get { return _maxColumnWidth; }
			set { _maxColumnWidth = value; }
		} int _maxColumnWidth = 500;

		/// <summary>
		/// Should the list give a different background color to every second row?
		/// </summary>
		/// <remarks>The color of the alternate rows is given by AlternateRowBackColor. </remarks>
		[Category("Appearance")]
		[Description("Should the list view use a different backcolor to alternate rows?")]
		[DefaultValue(false)]
		public bool UseAlternatingBackColors
		{
			get { return _useAlternatingBackColors; }
			set { _useAlternatingBackColors = value; }
		} bool _useAlternatingBackColors;

		/// <summary>
		/// If every second row has a background different to the control, what color should it be?
		/// </summary>
		[Category("Appearance")]
		[Description("If using alternate colors, what color should alterate rows be?")]
		public Color AlternateRowBackColor
		{
			get { return _alternateRowBackColor; }
			set { _alternateRowBackColor = value; }
		} Color _alternateRowBackColor;

		/// <summary>
		/// The text to display when there are no list items
		/// </summary> 
		[Category("Appearance")]
		[Description("The text to display when there are no list items")]
		[DefaultValue("There are no items to show in this view")]
		public string NoItemsMessage
		{
			get { return _noItemsMessage; }
			set
			{
				if (_noItemsMessage != value)
				{
					_noItemsMessage = value;
					noItemsWidth = -1;
					this.Invalidate();
					this.Update();
				}
			}
		} string _noItemsMessage = "There are no items to show in this view";

		/// <summary>
		/// When a group Title has an item count, how should the lable be formatted?
		/// </summary>
		/// <remarks>
		/// The given format string has two placeholders:
		/// <list type="bullet">
		/// <item>{0} - the original group Title</item>
		/// <item>{1} - the number of items in the group</item>
		/// </list>
		/// </remarks>
		/// <example>"{0} [{1} items]"</example>
		[Category("Appearance")]
		[Description("The format to use when suffixing item counts to group titles.\r\nExample: '{0} ( {1} records )' where 0 = original group Title, and 1 = number of items in the group")]
		[DefaultValue(null)]
		public string GroupItemCountFormat
		{
			get { return _groupItemCountFormat; }
			set { _groupItemCountFormat = value; }
		} string _groupItemCountFormat;

		#endregion

		#region Configuration
		/// <summary>
		/// Gets or sets whether or not to allow showing the ListView items in groups
		/// </summary>
		[Category("Configuration")]
		[Description("Gets or sets whether or not to allow showing the ListView items in groups")]
		[DefaultValue(true)]
		public bool AllowShowGroups
		{
			get { return _allowShowGroups; }
			set
			{
				if (this._allowShowGroups != value)
				{
					this._allowShowGroups = value;
					if (!value)
						this.IsGrouped = value;
					if (null != this._settings && this._settings.AllowShowGroups != value)
					{
						this._settings.AllowShowGroups = value;
						if (this._settings.ShouldPersist)
							viewMgr.Persist(this._settings);
					}
				}
			}
		} bool _allowShowGroups = true;

		/// <summary>
		/// Gets or sets whether or not to allow sorting of the ListView items
		/// </summary>
		[Category("Configuration")]
		[Description("Gets or sets whether or not to allow sorting of the ListView items")]
		[DefaultValue(true)]
		public bool AllowSort
		{
			get { return _allowSort; }
			set
			{
				if (this._allowSort != value)
				{
					this._allowSort = value;
					if (!value)
						this.IsSorted = value;
					if (null != this._settings && this._settings.AllowSort != value)
					{
						this._settings.AllowSort = value;
						if (this._settings.ShouldPersist)
							viewMgr.Persist(this._settings);
					}
				}
			}
		} bool _allowSort = true;

		/// <summary>
		/// Gets or sets the Column Index that the List is arranged by (sorted column).
		/// </summary>
		[Category("Configuration")]
		[Description("Gets or sets the Column Index that the List is arranged by (sorted column).")]
		[DefaultValue(0)]
		public int ArrangedBy
		{
			get
			{
				if (null != this._settings)
					_arrangedBy = this._settings.ArrangedBy;

				return _arrangedBy;
			}
			set
			{
				if (this._arrangedBy != value)
				{
					this._arrangedBy = value;
					if (null != this._settings)
					{
						this._settings.ArrangedBy = value;
						if (this._settings.ShouldPersist)
							viewMgr.Persist(this._settings);
					}
				}
			}
		} int _arrangedBy = 0;

		/// <summary>
		/// Gets or sets which direction the DataListView is sorted.
		/// </summary>
		[Category("Configuration")]
		[Description("Gets or sets which direction the DataListView is sorted.")]
		[DefaultValue(typeof(SortOrder), "None")]
		public SortOrder SortOrder
		{
			get
			{
				if (this._sortOrder == SortOrder.None && null != this._settings && this._settings.LastSortOrder != SortOrder.None)
					this._sortOrder = this._settings.LastSortOrder;
				return this._sortOrder;
			}
			set
			{
				if (this._sortOrder != value)
				{
					this._sortOrder = value;
					if (null != this._settings && this._settings.LastSortOrder != value)
					{
						this._settings.LastSortOrder = value;
						if (this._settings.ShouldPersist)
							viewMgr.Persist(this._settings);
					}
				}
				else if (null != this._settings && this._settings.LastSortOrder != value)
				{
					this._settings.LastSortOrder = value;
					if (this._settings.ShouldPersist)
						viewMgr.Persist(this._settings);
				}
			}
		} SortOrder _sortOrder = SortOrder.None;

		/// <summary>
		/// Gets or sets whether or not the DataListView is sorted.
		/// </summary>
		[Category("Configuration")]
		[Description("Gets or sets whether or not the DataListView is sorted.")]
		[DefaultValue(false)]
		public bool IsSorted
		{
			get
			{
				if (null != this._settings)
					_isSorted = this._settings.IsSorted;

				return _isSorted;
			}
			set
			{
				if (!this.AllowSort && value)
					throw new InvalidOperationException("Sorting is not allowed.");
				_isSorted = value;
				if (value)
					this._isGrouped = !value;
				if (null != this._settings)
				{
					this._settings.IsSorted = value;
					if (value)
						this._settings.IsGrouped = !value;
					if (this._settings.ShouldPersist)
						viewMgr.Persist(this._settings);
				}
				if (!value)
				{
					Application.DoEvents();
					this.RefreshView();
				}
			}
		} bool _isSorted = false;

		/// <summary>
		/// Indicated whether or not the list view is grouped
		/// </summary>
		[Category("Configuration")]
		[Description("Gets or sets a value indicating whether items are displayed in groups.")]
		[DefaultValue(false)]
		public bool IsGrouped
		{
			get { return this._isGrouped; }
			set
			{
				if (!this.AllowShowGroups && value)
					throw new InvalidOperationException("Grouping is not allowed.");
				this._isGrouped = value;
				this.ShowGroups = value;
				if (!value)
					this.RefreshView();
				if (value)
					this._isSorted = !value;
				if (null != this._settings)
				{
					this._settings.IsGrouped = value;
					if (value)
						this._settings.IsSorted = !value;
					if (this._settings.ShouldPersist)
						viewMgr.Persist(this._settings);
				}
			}
		} bool _isGrouped = false;

		/// <summary>
		/// Gets or sets whether or not the Configuration properties are persisted.
		/// </summary>
		[Category("Configuration")]
		[Description("Gets or sets whether or not the Configuration properties are persisted.")]
		[DefaultValue(false)]
		public bool PersistConfiguration
		{
			get { return _persistConfiguration; }
			set
			{
				if (_persistConfiguration != value)
				{
					_persistConfiguration = value;

					if (null != this._settings)
					{
						this._settings.ShouldPersist = _persistConfiguration;
						if (_persistConfiguration && !this._settings.IsPersisted)
						{
							viewMgr.Persist(this._settings);
						}
						else if (!_persistConfiguration && this._settings.IsPersisted)
						{
							viewMgr.Remove(this._settings.Name);
							this._settings.IsPersisted = false;
							viewMgr.Add(this._settings);
						}
					}
				}
			}
		} bool _persistConfiguration = false;

		/// <summary>
		/// Gets or sets the name for the Configuration. This enables the ability to have more than one version of Configuration settings.
		/// </summary>
		[Category("Configuration")]
		[Description("Gets or sets the name for the Configuration. This enables the ability to have more than one version of Configuration settings.")]
		[DefaultValue("DataListView")]
		public string ViewName
		{
			get { return _viewName; }
			set
			{
				if (string.IsNullOrEmpty(value))
					throw new FieldAccessException("Cannot set the ViewName to string.empty");
				_viewName = value;
				if (!this.Initializing)
					this.InitializeConfiguration();
			}
		} string _viewName = "DataListView";

		#endregion

		#region Non-Designable

		/// <summary>
		/// The Last or Current ViewSettings
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public ViewSettings Settings
		{
			get { return _settings; }
			//set { _settings = value; }
		} ViewSettings _settings;

		/// <summary>
		/// Gets or sets the Editor Form. The Form must be of type or inherit from <see cref="HSS.Forms.DetailModuleContainer"/>
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Type EditorForm
		{
			get { return _editorForm; }
			set
			{
				Guard.TypeIsAssignable(typeof(HSS.Forms.DetailModuleContainer), value, "EditorForm");
				_editorForm = value;
			}
		} Type _editorForm;

		/// <summary>
		/// The Type of the editor for the items contained in this view. The Control must inherit from <see cref="HSS.Forms.DetailModuleBase"/>
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Type EditorControl
		{
			get { return _editorControl; }
			set
			{
				Guard.TypeIsAssignable(typeof(HSS.Forms.DetailModuleBase), value, "EditorControl");
				_editorControl = value;
			}
		} Type _editorControl;

		/// <summary>
		/// AlternateRowBackColorOrDefault
		/// </summary>
		[Browsable(false)]
		private Color AlternateRowBackColorOrDefault
		{
			get
			{
				if (_alternateRowBackColor == Color.Empty)
					//return Color.LemonChiffon;
					return Color.AliceBlue; // very slight
				else
					return _alternateRowBackColor;
			}
		}

		/// <summary>
		/// Get or set whether or not the listview is currently Initializing.
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool Initializing
		{
			get { return _initializingCount > 0; }
			set
			{
				if (value)
					this._initializingCount = 1;
				else if (this._initializingCount > 0)
				{
					this._initializingCount = 1;
					this.DecreaseInitializingCount();
				}
			}
		} int _initializingCount;

		/// <summary>
		/// Indicates if the data has been modified
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool IsDirty
		{
			get { return _dirty; }
			set
			{
				_dirty = value;
				InvokeDirty(this, new DirtyEventArgs(value));
			}
		} bool _dirty;

		#endregion

		#endregion

		#region Overrides

		#region Methods

		/// <summary>
		/// If there are no items in the list writes a message stating so
		/// </summary>
		/// <param name="m">A Windows Message</param>
		protected override void WndProc(ref Message m)
		{
			base.WndProc(ref m);

			if (m.Msg == WM_PAINT && this.Items.Count == 0 && !this.DesignMode)
			{
				using (Graphics g = this.CreateGraphics())
				{
					g.Clear(this.BackColor);
					if (noItemsWidth == -1)
					{
						SizeF sf = g.MeasureString(_noItemsMessage, this.Font);
						noItemsWidth = sf.ToSize().Width;
					}
					int left = ((this.Width - noItemsWidth) / 2) - gutter;
					g.DrawString(_noItemsMessage, this.Font, Brushes.Black, left, noItemsTop);
				}
			}
		}

		/// <summary>
		/// Refresh the Watermark if no items are in the list.
		/// </summary>
		/// <param name="e">EventArgs</param>
		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);
			if (this.Items.Count == 0)
				this.Invalidate();
		}

		/// <summary>
		/// OnKeyUp
		/// </summary>
		/// <param name="e">KeyEventArgs</param>
		protected override void OnKeyUp(KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Delete && this.SelectedItems.Count > 1)
				this.OnDeleteSelectedItems();
			else if (e.KeyCode == Keys.Delete && this.SelectedItems.Count == 1)
				this.OnDeleteSelectedItem(new ItemDeleteEventArgs(this.SelectedItems[0].Index));
			else if (e.KeyCode == Keys.F2 && this.SelectedItems.Count > 0 && this.LabelEdit)
				this.SelectedItems[0].BeginEdit();
			else if ((e.Modifiers == Keys.Control && e.KeyCode == Keys.X) && this.SelectedItems.Count > 0 && this.AllowCut)
				this.Cut();
			else if ((e.Modifiers == Keys.Control && e.KeyCode == Keys.C) && this.SelectedItems.Count > 0 && this.AllowCopy)
				this.Copy();
			else if ((e.Modifiers == Keys.Control && e.KeyCode == Keys.V) && this.SelectedItems.Count > 0 && this.AllowPaste)
				this.Paste();
			else
				base.OnKeyUp(e);
		}

		/// <summary>
		/// OnColumnClick
		/// </summary>
		/// <param name="e">ColumnClickEventArgs</param>
		protected override void OnColumnClick(ColumnClickEventArgs e)
		{
			base.OnColumnClick(e);

			if (base.Items.Count == 0)
				return;

			if (e.Column == this.ArrangedBy)
				this.SortOrder = (this.SortOrder == SortOrder.Descending ? SortOrder.Ascending : SortOrder.Descending);
			else
				this.SortOrder = SortOrder.Ascending;

			if (this.AllowSort && !this.IsGrouped)
				this.Sort(e.Column);

			if (this.AllowShowGroups && !this.IsSorted)
				this.Group(e.Column);
		}

		/// <summary>
		/// Sorts the items of the list view.
		/// </summary>
		public new void Sort()
		{
			this.Sort(this.ArrangedBy);
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets how items are displayed in the control.
		/// </summary>
		/// <returns>One of the <see cref="System.Windows.Forms.View"></see> values. The default is <see cref="System.Windows.Forms.View.LargeIcon"></see>.</returns>
		/// <exception cref="System.ComponentModel.InvalidEnumArgumentException">The value specified is not one of the <see cref="System.Windows.Forms.View"></see> values. </exception>
		[Category("Appearance")]
		[Description("Gets or sets how items are displayed in the control.")]
		[DefaultValue(0)]
		public new View View
		{
			get { return base.View; }
			set
			{
				if (this.DesignMode)
				{
					base.View = value;
					return;
				}

				if (base.View == value)
				{
					#region Store Settings
					if (null != this._settings)
					{
						if (this._settings.View != value)
						{
							this._settings.View = value;
							if (this._settings.ShouldPersist)
								viewMgr.Persist(this._settings);
						}
					}
					#endregion

					return;
				}

				if (this.Initializing)
				{
					if (value == View.Tile)
					{
						this.Clear();
						this.ListViewItemSorter = null;
						if (this.tileViewColumns.Count > 0)
						{
							this.Columns.AddRange(this.tileViewColumns.ToArray());
							this.CalculateReasonableTileSize();
						}
					}

					base.View = value;

					#region Store Settings
					if (null != this._settings)
					{
						if (this._settings.View != value)
						{
							this._settings.View = value;
							if (this._settings.ShouldPersist)
								viewMgr.Persist(this._settings);
						}
					}
					#endregion

					return;
				}

				#region Store Settings
				if (null != this._settings)
				{
					this._settings.View = value;
					if (this._settings.ShouldPersist)
						viewMgr.Persist(this._settings);
				}
				#endregion

				this.IncreaseInitializingCount();

				#region Modify Columns (Detail/Tile)

				if (value == View.Tile)
				{
					this.Clear();
					this.ListViewItemSorter = null;
					if (this.tileViewColumns.Count > 0)
					{
						this.Columns.AddRange(this.tileViewColumns.ToArray());
						this.CalculateReasonableTileSize();
					}
				}

				if (value == View.Details)
				{
					this.Clear();
					this.ListViewItemSorter = null;
					if (this.detailViewColumns.Count > 0)
						this.Columns.AddRange(this.detailViewColumns.ToArray());
				}

				#endregion

				base.View = value;

				this.DecreaseInitializingCount();

			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether items are displayed in groups.
		/// </summary>
		/// <returns>true to display items in groups; otherwise, false. The default value is true.</returns>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public new bool ShowGroups
		{
			get { return base.ShowGroups; }
			set { base.ShowGroups = value; }
		}

		/// <summary>
		/// Gets or sets the sort order for items in the control.
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public new SortOrder Sorting
		{
			get { return base.Sorting; }
			set { base.Sorting = value; }
		}

		/// <summary>
		/// Return Columns for this list. We hide the original so we can associate
		/// a specialised editor with it.
		/// </summary>
		[Editor(typeof(DataListColumnCollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
		public new ListView.ColumnHeaderCollection Columns
		{
			get { return base.Columns; }
		}

		/// <summary>
		/// Gets or sets the System.Windows.Forms.ImageList to use when displaying items as small icons in the control
		/// </summary>
		[DefaultValue("")]
		public new ImageList SmallImageList
		{
			get { return base.SmallImageList; }
			set
			{
				if (null == value && null != base.SmallImageList)
					base.SmallImageList = new ImageList() { ImageSize = new Size(1, base.SmallImageList.ImageSize.Height) };
				base.SmallImageList = value;
				this.RefreshView();
			}
		}

		/// <summary>
		/// Gets or sets the System.Windows.Forms.ImageList to use when displaying items as large icons in the control
		/// </summary>
		[DefaultValue("")]
		public new ImageList LargeImageList
		{
			get { return base.LargeImageList; }
			set
			{
				if (null == value && null != base.LargeImageList)
					base.LargeImageList = new ImageList() { ImageSize = new Size(1, 1) };
				base.LargeImageList = value;
				this.RefreshView();
			}
		}

		#endregion

		#endregion

		#region Methods

		#region Actions

		#region LoadData
		/// <summary>
		/// Set the list of type IDataContainer that will be shown in this list view.
		/// </summary>
		/// <param name="listData">The list of type IDataContainer to be displayed</param>
		/// <exception cref="ArgumentNullException">You cannot pass null for listData.</exception>
		public void LoadData(IEnumerable<IDataContainer> listData)
		{
			if (listData == null)
				throw new ArgumentNullException("listData");

			if (this.Columns.Count == 0)
				throw new ApplicationException("Missing the Columns. They must be set prior to loading data.");

			this.SetData(listData);
			this.RefreshView();
		}
		#endregion

		#region RefreshView
		/// <summary>
		/// Refresh the list view items
		/// </summary>
		public void RefreshView()
		{
			if (this.Initializing)
				return;

			if (UseBeginEndUpdate)
				this.BeginUpdate();

			try
			{
				if (null != this.dataListItems)
					this.dataListItems.Clear();
				this.dataListItems = new List<DataListItem>();
				this.ResetColummsWidth();

				#region Build Items
				if (null != this.dataSource)
				{
					using (Graphics g = this.CreateGraphics())
					{
						g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
						g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;

						foreach (IDataContainer data in this.dataSource.Values)
						{
							DataListItem item = new DataListItem(data);
							this.BuildItem(g, data, item);
							this.dataListItems.Add(item);
						}
					}
				}
				#endregion

				this.ApplyFilter();
				this.SetColumnsWidth();
			}
			finally
			{
				if (UseBeginEndUpdate)
					this.EndUpdate();
			}
		}
		#endregion

		#region View Manipulation

		/// <summary>
		/// Will adjust the columnwidht to the largest item in the list, or the width 
		/// of the columnheader, which ever is wider.
		/// </summary>
		public void SetColumnsWidth()
		{
			if (this.AutoSizeColumns)
			{
				if (this.Items.Count == 0)
					this.MeasureColumnHeaders();

				foreach (DataListColumn col in this.Columns)
				{
					int curWidth = col.Width;
					if (col.PreviousWidth == curWidth || col.PreviousWidth == 0)
					{
						int newWidth = System.Convert.ToInt32(Math.Ceiling((double)col.LargestWidth));
						if (newWidth != curWidth)
						{
							col.Width = newWidth;
							col.PreviousWidth = newWidth;
						}
					}
				}
			}
		}

		/// <summary>
		/// Sort the items in the list view by the values in the given column.
		/// </summary>
		/// <param name="columnIndex">The index of the column whose values will be used for the sorting</param>
		public void Sort(int columnIndex)
		{
			if (!this.AllowSort)
			{
				this.IsSorted = false;
				this.SortOrder = SortOrder.None;
				this.Sorting = SortOrder.None;
				return;
			}

			if (columnIndex >= this.Columns.Count)
				columnIndex = 0;

			this.ArrangedBy = columnIndex;

			if (this.Columns.Count == 0)
				return;

			DataListColumn column = this.Columns[columnIndex] as DataListColumn;

			if (column == null)
				return;

			this.Sort(column);
		}

		/// <summary>
		/// Sort the items in the list view by the values in the given column.
		/// </summary>
		/// <param name="column">The index of the column whose values will be used for the sorting</param>
		public void Sort(DataListColumn column)
		{
			if (!this.AllowSort)
			{
				this.IsSorted = false;
				return;
			}

			this.Groups.Clear();
			this.IsSorted = true;
			this.ArrangedBy = column.Index;

			if (this.SortOrder == SortOrder.None)
				this.SortOrder = SortOrder.Ascending;

			this.ListViewItemSorter = new DataListColumnComparer(column, this.SortOrder);

			if (this.View == View.Details)
			{
				this.ShowSortIndicator(column, this.SortOrder);
				if (this.Items.Count > 0 && this.UseAlternatingBackColors)
					this.ColorAlternateLines();
			}
		}

		/// <summary>
		/// Show the view in groups, based on the given column
		/// </summary>
		public void Group()
		{
			this.Group(this.ArrangedBy);
		}

		/// <summary>
		/// Show the view in groups, based on the given column
		/// </summary>
		/// <param name="groupColumnIndex">The index of the column whose values should be used for grouping.</param>
		public void Group(int groupColumnIndex)
		{
			this.Group(groupColumnIndex, this.SortGroupItemsColumn);
		}

		/// <summary>
		/// Show the view in groups, based on the given column
		/// </summary>
		/// <param name="groupColumnIndex">The index of the column whose values should be used for grouping.</param>
		/// <param name="sortColumnIndex">The index of the column whose values should be used for sorting within each group.</param>
		public void Group(int groupColumnIndex, int sortColumnIndex)
		{
			if (!this.AllowShowGroups)
			{
				this.IsGrouped = false;
				this.Groups.Clear();
				return;
			}

			this.Groups.Clear();
			this.IsGrouped = true;
			this.ArrangedBy = groupColumnIndex;
			if (this.SortOrder == SortOrder.None)
				this.SortOrder = SortOrder.Ascending;

			if (this.Columns.Count == 0 || groupColumnIndex < 0)
				return;
			if (groupColumnIndex >= this.Columns.Count)
				groupColumnIndex = 0;
			if (!(this.Columns[groupColumnIndex] is DataListColumn))
				return;
			if (sortColumnIndex < 0)
				sortColumnIndex = groupColumnIndex;
			if (sortColumnIndex >= this.Columns.Count)
				sortColumnIndex = 0;

			Dictionary<object, List<DataListItem>> map = new Dictionary<object, List<DataListItem>>();
			DataListColumn groupColumn = (DataListColumn)this.Columns[groupColumnIndex];
			DataListColumn sortColumn = (DataListColumn)this.Columns[sortColumnIndex];

			#region Seperate the list view items into groups, using the group key as the separator
			foreach (DataListItem dlvi in this.Items)
			{
				object key = groupColumn.GetGroupKey(dlvi.Data);
				if (key == null) key = "{null}"; // null can't be used as the key for a dictionary
				if (!map.ContainsKey(key)) map[key] = new List<DataListItem>();
				map[key].Add(dlvi);
			}
			#endregion

			#region Create a list of the groups, setting the header formatting
			List<ListViewGroup> groups = new List<ListViewGroup>();
			if (this.ShowItemCountOnGroups)
			{
				string fmt = this.GroupItemCountFormat;
				if (String.IsNullOrEmpty(fmt))
				{
					#region Default Format
					foreach (object key in map.Keys)
					{
						ListViewGroup lvg = new ListViewGroup(groupColumn.ConvertGroupKeyToTitle(key));
						lvg.Tag = key;
						int count = map[key].Count;
						fmt = "{0} ( {1} " + ((count == 1) ? "item" : "items") + " )";
						lvg.Header = string.Format(fmt, lvg.Header, count);
						groups.Add(lvg);
					}
					#endregion
				}
				else
				{
					#region Custom Format
					foreach (object key in map.Keys)
					{
						ListViewGroup lvg = new ListViewGroup(groupColumn.ConvertGroupKeyToTitle(key));
						lvg.Tag = key;
						int count = map[key].Count;
						lvg.Header = string.Format(fmt, lvg.Header, count);
						groups.Add(lvg);
					}
					#endregion
				}
			}
			else
			{
				#region No Format
				foreach (object key in map.Keys)
				{
					ListViewGroup lvg = new ListViewGroup(groupColumn.ConvertGroupKeyToTitle(key));
					lvg.Tag = key;
					groups.Add(lvg);
				}
				#endregion
			}
			#endregion

			#region Then sort the groups themselves
			groups.Sort(new ListViewGroupComparer(this.SortOrder));
			#endregion

			#region Add the groups to the list and the list items to the group
			foreach (ListViewGroup group in groups)
			{
				this.Groups.Add(group);
				if (this.SortGroupItems)
					map[group.Tag].Sort(new DataListItemComparer(this.SortOrder, sortColumn));
				group.Items.AddRange(map[group.Tag].ToArray());
			}
			#endregion

			if (this.View == View.Details)
			{
				this.ShowSortIndicator(groupColumn, this.SortOrder);
				if (this.Items.Count > 0 && this.UseAlternatingBackColors)
					this.ColorAlternateLinesByGroups();
			}
		}

		/// <summary>
		/// Returns the list of data
		/// </summary>
		/// <returns>A List of IDataContainer objects</returns>
		public List<IDataContainer> GetData()
		{
			List<IDataContainer> items = new List<IDataContainer>();
			foreach (DataListItem item in this.Items)
				items.Add(item.Data);
			return items;
		}

		/// <summary>
		/// Returns a list of data for each selected row if any
		/// </summary>
		/// <returns>A List of IDataContainer objects</returns>
		public List<IDataContainer> GetSelectedData()
		{
			List<IDataContainer> items = new List<IDataContainer>();
			foreach (DataListItem lvi in this.SelectedItems)
				items.Add(lvi.Data);
			return items;
		}

		/// <summary>
		/// Returns a list of data for each dirty row if any
		/// </summary>
		/// <returns>A List of dirty IDataContainer objects</returns>
		public List<IDataContainer> GetDirtyData()
		{
			List<IDataContainer> items = new List<IDataContainer>();
			foreach (DataListItem item in this.Items)
			{
				if (item.IsDirty)
					items.Add(item.Data);
			}
			return items;
		}

		/// <summary>
		/// Once the dirty rows have been saved (persisted), set dirty to false
		/// </summary>
		public void ResetDirtyData()
		{
			foreach (DataListItem item in this.Items)
			{
				if (item.IsDirty)
					item.IsDirty = false;
			}
			this.IsDirty = false;
		}

		/// <summary>
		/// Converts the selected items to Xml
		/// </summary>
		/// <param name="selectedDataListItems">The list items to put on the clipboard</param>
		/// <returns>The Clipboard DataObject</returns>
		private DataObject SetClipboardData(List<DataListItem> selectedDataListItems)
		{
			if (null != selectedDataListItems && selectedDataListItems.Count > 0)
			{
				string count = selectedDataListItems.Count.ToString();
				string Xml = string.Empty;
				StringBuilder strXml = new StringBuilder();
				StringBuilder strTypes = new StringBuilder();
				foreach (DataListItem item in selectedDataListItems)
				{
					Xml = item.Data.ToXml();
					if (strXml.Length > 0)
						strXml.AppendLine(Environment.NewLine + XmlClipSeparator);
					strXml.Append(Xml);
					if (strTypes.Length > 0)
						strTypes.Append(",");
					strTypes.Append(item.Data.GetType().FullName);
				}

				DataObject dobj = new DataObject();

				// Set the Count as StringFormat
				dobj.SetData(DataFormats.StringFormat, count);

				// Set the Items Types as Comma Separated List
				dobj.SetData(DataFormats.CommaSeparatedValue, strTypes.ToString());

				// Set the Items Xml as UnicodeText
				dobj.SetData(DataFormats.UnicodeText, strXml.ToString());

				return dobj;
			}

			return null;
		}

		/// <summary>
		/// Converts the data from the Clipboard back to their original form
		/// </summary>
		/// <param name="dataObject">The clipboard DataObject to deserialize</param>
		/// <returns>The List of dataListItems</returns>
		private List<IDataContainer> GetClipboardData(DataObject dataObject)
		{
			List<IDataContainer> newItems = new List<IDataContainer>();
			object data = dataObject.GetData(DataFormats.StringFormat);

			if (null != data)
			{
				int count = 0;
				string strCount = (string)data;
				if (!string.IsNullOrEmpty(strCount))
					count = int.Parse(strCount);

				string[] Xmls = ((string)dataObject.GetData(DataFormats.UnicodeText)).Split(new string[] { XmlClipSeparator }, count, StringSplitOptions.None);
				string[] Types = ((string)dataObject.GetData(DataFormats.CommaSeparatedValue)).Split(new string[] { "," }, count, StringSplitOptions.None);

				for (int i = 0; i < count; i++)
				{
					Type t = Type.GetType(Types[i]);
					IDataContainer config = (IDataContainer)ObjectSerialization.FromXml(Xmls[i], t);
					newItems.Add(config);
				}

				return newItems;

			}

			return null;
		}

		/// <summary>
		/// Begin editing the currently selected item if one is selected
		/// </summary>
		public void Edit()
		{
			if (!this.AllowEdit)
				return;

			if (null == this.EditorForm)
				return;

			if (null == this.EditorControl)
				return;

			if (this.SelectedItems.Count > 0)
				this.Edit((DataListItem)this.SelectedItems[0]);
		}

		/// <summary>
		/// Begin editing the specified item
		/// </summary>
		/// <param name="item">The DataListItem to Edit</param>
		public void Edit(DataListItem item)
		{
			if (!this.AllowEdit)
				return;

			if (null == this.EditorForm)
				return;

			if (null == this.EditorControl)
				return;

			if (null != item)
				item.Edit(this);
		}

		/// <summary>
		/// Copies the selected items(s) to the clipboard and then deletes them
		/// </summary>
		public void Cut()
		{
			if (!this.AllowCopy)
				return;

			if (!this.AllowDelete)
				return;

			if (this.SelectedItems.Count > 0)
			{
				List<DataListItem> items = new List<DataListItem>();

				foreach (DataListItem item in this.SelectedItems)
					items.Add(item);

				if (items.Count > 0)
				{
					DataObject dobj = SetClipboardData(items);
					Clipboard.SetDataObject(dobj, false);
					this.Delete(items);
				}
			}
		}

		/// <summary>
		/// Copies the selected items(s) to the clipboard
		/// </summary>
		public void Copy()
		{
			if (!this.AllowCopy)
				return;

			if (this.SelectedItems.Count > 0)
			{
				List<DataListItem> items = new List<DataListItem>();

				foreach (DataListItem item in this.SelectedItems)
					items.Add(item);

				if (items.Count > 0)
				{
					DataObject dobj = SetClipboardData(items);
					Clipboard.SetDataObject(dobj, false);
				}
			}
		}

		/// <summary>
		/// Perform the paste operation by extracting the information from the clipboard and adding to the DataListView
		/// </summary>
		public void Paste()
		{
			if (!this.AllowPaste)
				return;

			DataObject dataObject = (DataObject)Clipboard.GetDataObject();
			List<IDataContainer> items = GetClipboardData(dataObject);

			using (Graphics g = this.CreateGraphics())
			{
				g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
				g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
				foreach (IDataContainer data in items)
				{
					// Rename
					int idIndex = 0;
					string newId = data.Id;
					while (this.dataSource.ContainsKey(newId))
					{
						idIndex++;
						newId = newId + idIndex.ToString();
					}
					if (idIndex > 0)
						data.Id = newId;
					DataListItem item = new DataListItem(data);
					this.dataSource.Add(newId, data);
					this.BuildItem(g, data, item);
					this.dataListItems.Add(item);
				}
			}

			this.ApplyFilter();
			this.SetColumnsWidth();
		}

		/// <summary>
		/// Delete the first selected item if one is selected
		/// </summary>
		public void Delete()
		{
			if (this.AllowDelete)
			{
				if (this.SelectedItems.Count > 0)
				{
					DataListItem item = this.SelectedItems[0] as DataListItem;
					if (null != item)
						this.Delete(item);
				}
			}
		}

		/// <summary>
		/// Deletes the requested item
		/// </summary>
		public void Delete(DataListItem item)
		{
			if (null == item.Data)
				return;

			if (string.IsNullOrEmpty(item.Data.Id))
				return;

			if (this.AllowDelete)
			{
				string deleteKey = string.Empty;
				foreach (string key in this.dataSource.Keys)
				{
					if (key == item.Data.Id)
					{
						deleteKey = key;
						break;
					}
				}
				if (!string.IsNullOrEmpty(deleteKey))
				{
					this.dataSource.Remove(deleteKey);
					item.Remove();
				}
			}
		}

		/// <summary>
		/// Deletes the provided list of dataListItems
		/// </summary>
		/// <param name="dataListItems">List of type DataListItem to delete</param>
		public void Delete(List<DataListItem> dataListItems)
		{
			if (this.AllowDelete)
			{
				foreach (DataListItem item in dataListItems)
					this.Delete(item);
			}
		}

		/// <summary>
		/// Deletes all the selected items from the DataListView
		/// </summary>
		public void DeleteSelection()
		{
			if (this.AllowDelete)
			{
				if (this.SelectedItems.Count > 0)
				{
					List<DataListItem> items = new List<DataListItem>();
					foreach (DataListItem item in this.SelectedItems)
						items.Add(item);
					this.Delete(items);
				}
			}
		}

		/// <summary>
		/// Select all items
		/// </summary>
		public void SelectAll()
		{
			if (this.AllowSelectAll)
			{
				foreach (ListViewItem item in this.Items)
					item.Selected = true;
			}
		}

		#endregion

		#endregion

		#region Helpers

		private void InitializeConfiguration()
		{
			if (this.DesignMode)
				return;

			if (null == this.viewMgr)
				this.viewMgr = new ViewManager(this);

			if (!viewMgr.Contains(_viewName))
			{
				this._settings = new ViewSettings(_viewName, base.View, this._arrangedBy, this._allowShowGroups, this._isGrouped, this._allowSort, this._isSorted, this._sortOrder, this._autoSizeColumns, this._persistConfiguration);
				viewMgr.Add(this._settings);
			}
			else
			{
				if (null == this._settings || this._settings.Name != _viewName)
					this._settings = viewMgr.Get(_viewName);

				if (null == this._settings)
				{
					this._settings = new ViewSettings(_viewName, base.View, this._arrangedBy, this._allowShowGroups, this._isGrouped, this._allowSort, this._isSorted, this._sortOrder, this._autoSizeColumns, this._persistConfiguration);
					viewMgr.Add(this._settings);
				}
				else
				{
					this._isGrouped = this._settings.IsGrouped;
					this._isSorted = this._settings.IsSorted;
					this._allowShowGroups = this._settings.AllowShowGroups;
					this._allowSort = this._settings.AllowSort;
					this._sortOrder = this._settings.LastSortOrder;
					this._arrangedBy = this._settings.ArrangedBy;
					this._autoSizeColumns = this._settings.AutoSizeColumns;
					if (this._persistConfiguration)
						this.View = this._settings.View;
				}

				this._settings.ShouldPersist = this._persistConfiguration;

				if (_persistConfiguration && !this._settings.IsPersisted)
				{
					viewMgr.Persist(this._settings);
				}
				else if (!_persistConfiguration && this._settings.IsPersisted)
				{
					viewMgr.Remove(this._settings.Name);
					this._settings.IsPersisted = false;
					viewMgr.Add(this._settings);
				}
			}
		}

		/// <summary>
		/// Increase the lock count.
		/// </summary>
		private void IncreaseInitializingCount()
		{
			_initializingCount++;
		}

		/// <summary>
		/// Unlock the listview. If this call is the last lock,
		/// the listview will be Refreshed.
		/// </summary>
		private void DecreaseInitializingCount()
		{
			if (_initializingCount <= 0)
				return;

			_initializingCount--;
			if (_initializingCount == 0 && !this.DesignMode)
				this.RefreshView();
		}

		/// <summary>
		/// Place the listData into a Dictionary
		/// </summary>
		/// <param name="listData">The list of IDataContainer Objects</param>
		private void SetData(IEnumerable<IDataContainer> listData)
		{
			#region Detail View Columns

			// Detail View has all columns
			this.detailViewColumns.Clear();
			foreach (DataListColumn column in this.Columns)
				this.detailViewColumns.Add(column);

			#endregion

			#region Tile View Columns

			// Tile View only has columns where IsTileColumn is true or the first columns (which is required)
			this.tileViewColumns.Clear();
			foreach (DataListColumn column in this.Columns)
			{
				if (column.Index == 0 || column.IsTileViewColumn)
					this.tileViewColumns.Add(column);
			}
			if (this.tileViewColumns.Count > 0)
				this.CalculateReasonableTileSize();
			#endregion

			this.ResetColummsWidth();

			if (null != this.dataSource)
				this.dataSource.Clear();
			this.dataSource = new Dictionary<string, IDataContainer>();
			foreach (IDataContainer item in listData)
				this.dataSource.Add(item.Id, item);
		}

		/// <summary>
		/// Builds a given DataListItem with values from the given data
		/// </summary>
		/// <param name="g"></param>
		/// <param name="data"></param>
		/// <param name="item"></param>
		internal void BuildItem(Graphics g, IDataContainer data, DataListItem item)
		{
			DataListColumn firstColumn = this.Columns[0] as DataListColumn;
			if (firstColumn != null)
			{
				#region First Column

				string val = firstColumn.GetStringValue(data);
				object tag = firstColumn.GetTag(data);

				int imageIndex = -1;
				if (null != firstColumn.ImageList)
					imageIndex = firstColumn.GetImage(data);

				#region Set Column's Largest Width

				float columnWidth = MeasureDisplayStringWidth(g, firstColumn.Text, this.Font);

				bool hasImage = false, hasCheckBox = this.CheckBoxes;
				if (null != firstColumn.ImageList && (imageIndex != -1)) hasImage = true;
				float itemWidth = MeasureDisplayStringWidth(g, val, this.Font, hasCheckBox, hasImage);

				if (itemWidth > columnWidth)
					columnWidth = itemWidth;

				if (columnWidth > firstColumn.LargestWidth)
					firstColumn.LargestWidth = columnWidth;

				#endregion

				item.Text = val;
				item.Tag = tag;
				item.ImageIndex = imageIndex;

				#endregion
			}

			#region SubItems
			for (int i = 1; i < this.Columns.Count; i++)
			{
				DataListColumn column = this.Columns[i] as DataListColumn;
				if (column == null)
					item.SubItems.Add("");
				else
				{
					string val = column.GetStringValue(data).Trim();

					#region Set Column's Largest Width

					float columnWidth = MeasureDisplayStringWidth(g, column.Text, this.Font);
					float itemWidth = MeasureDisplayStringWidth(g, val, this.Font);

					if (itemWidth > columnWidth)
						columnWidth = itemWidth;

					if (columnWidth > column.LargestWidth)
						column.LargestWidth = columnWidth;

					#endregion

					ListViewItem.ListViewSubItem lvsi;
					if (this.View == View.Tile)
						lvsi = item.SubItems.Add(val, Color.Gray, item.BackColor, item.Font);
					else
						lvsi = item.SubItems.Add(val);
					lvsi.Tag = column.GetTag(data);
				}
			}
			#endregion
		}

		/// <summary>
		/// Measure the columns header only, for when there are no items
		/// </summary>
		private void MeasureColumnHeaders()
		{

			if (null == this.Columns)
				return;

			using (Graphics g = this.CreateGraphics())
			{
				g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
				g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
				for (int i = 0; i < this.Columns.Count; i++)
				{
					DataListColumn column = this.Columns[i] as DataListColumn;
					if (null != column)
					{
						if (column.Text != string.Empty)
						{
							float columnWidth = MeasureDisplayStringWidth(g, column.Text, this.Font);
							if (columnWidth > column.LargestWidth)
								column.LargestWidth = columnWidth;
						}
					}
				}
			}
		}

		/// <summary>
		/// Reset the widths
		/// </summary>
		private void ResetColummsWidth()
		{
			if (null != this.Columns)
			{
				foreach (ColumnHeader col in this.Columns)
				{
					DataListColumn c = col as DataListColumn;
					if (null != c)
					{
						c.PreviousWidth = 0;
						c.LargestWidth = 0;
					}
				}
			}
		}

		/// <summary>
		/// Change the backcolor of every second row.
		/// </summary>
		private void ColorAlternateLines()
		{
			Color rowBackColor = this.AlternateRowBackColorOrDefault;

			foreach (ListViewItem lvi in this.Items)
			{
				if (lvi.Index % 2 == 0)
					lvi.BackColor = rowBackColor;
				else
					lvi.BackColor = this.BackColor;
			}
		}

		/// <summary>
		/// Change the backcolor of every second row, when the list is organised by groups.
		/// </summary>
		/// <remarks>Because groups are currently shown, the ordering of list items is different.
		/// In a straight list, <code>lvi.Index</code> is the display index, and can be used to determine
		/// whether the row should be colored. But when organised by groups, <code>lvi.Index</code> is not
		/// useable because it still refers to the position in the overall list, not the display order.
		/// So we have to walk each group and then the items in each group, counting as we go, to know
		/// which items should be back colored.</remarks>
		private void ColorAlternateLinesByGroups()
		{
			Color rowBackColor = this.AlternateRowBackColorOrDefault;

			int i = 0;
			foreach (ListViewGroup group in this.Groups)
			{
				foreach (ListViewItem lvi in group.Items)
				{
					if (i % 2 == 0)
						lvi.BackColor = rowBackColor;
					else
						lvi.BackColor = this.BackColor;
					i++;
				}
			}
		}

		/// <summary>
		/// Determine length of string
		/// </summary>
		/// <param name="graphics">A Graphics Object</param>
		/// <param name="text">The text to evaluate</param>
		/// <param name="font">The Font that will be applied to the text</param>
		/// <returns>The length of the text as a float value.</returns>
		private float MeasureDisplayStringWidth(Graphics graphics, string text, Font font)
		{
			return MeasureDisplayStringWidth(graphics, text, font, false, false);
		}

		/// <summary>
		/// Determine length of string
		/// </summary>
		/// <param name="graphics">A Graphics Object</param>
		/// <param name="text">The text to evaluate</param>
		/// <param name="font">The Font that will be applied to the text</param>
		/// <param name="hasCheckBox">Increases off for checkbox</param>
		/// <param name="hasImage">Increases off for image</param>
		/// <returns>The length of the text as a float value.</returns>
		private float MeasureDisplayStringWidth(Graphics graphics, string text, Font font, bool hasCheckBox, bool hasImage)
		{
			float offSet = 10.0F;

			if (hasCheckBox)
				offSet += 25.0F;

			if (hasImage)
				offSet += this.SmallImageList.ImageSize.Width;

			if (!string.IsNullOrEmpty(text))
			{
				SizeF widthF = graphics.MeasureString(text, font, this.MaxColumnWidth,
					new StringFormat(StringFormatFlags.MeasureTrailingSpaces));
				return widthF.Width + offSet;
			}
			else
			{
				return offSet + 25;
			}
		}

		/// <summary>
		/// Places a sort indicator in the header of the given column
		/// </summary>
		/// <param name="columnToSort">The column to be marked</param>
		/// <param name="sortOrder">The sort order in effect on that column</param>
		private void ShowSortIndicator(DataListColumn columnToSort, SortOrder sortOrder)
		{
			if (null != columnToSort)
			{
				int previouslySortedColumn = _previouslySortedColumn;
				int newSortColumn = columnToSort.Index;

				IntPtr hHeader = ListViewAPI.SendMessage(this.Handle, ListViewAPI.LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);
				IntPtr newColumn = new IntPtr(newSortColumn);
				IntPtr prevColumn = new IntPtr(previouslySortedColumn);
				ListViewAPI.HDITEM hdItem;
				IntPtr rtn;

				// Only update the previous item if it existed and if it was a different one.
				if (previouslySortedColumn != -1 && previouslySortedColumn != newSortColumn)
				{
					// Clear icon from the previous column.
					hdItem = new ListViewAPI.HDITEM();
					hdItem.mask = ListViewAPI.HDI_FORMAT;
					rtn = ListViewAPI.SendMessageITEM(hHeader, ListViewAPI.HDM_GETITEM, prevColumn, ref hdItem);
					hdItem.fmt &= ~ListViewAPI.HDF_SORTDOWN & ~ListViewAPI.HDF_SORTUP;
					rtn = ListViewAPI.SendMessageITEM(hHeader, ListViewAPI.HDM_SETITEM, prevColumn, ref hdItem);
				}

				// Set icon on the new column.
				hdItem = new ListViewAPI.HDITEM();
				hdItem.mask = ListViewAPI.HDI_FORMAT;
				rtn = ListViewAPI.SendMessageITEM(hHeader, ListViewAPI.HDM_GETITEM, newColumn, ref hdItem);
				if (sortOrder == SortOrder.Ascending)
				{
					hdItem.fmt &= ~ListViewAPI.HDF_SORTDOWN;
					hdItem.fmt |= ListViewAPI.HDF_SORTUP;
				}
				else
				{
					hdItem.fmt &= ~ListViewAPI.HDF_SORTUP;
					hdItem.fmt |= ListViewAPI.HDF_SORTDOWN;
				}
				rtn = ListViewAPI.SendMessageITEM(hHeader, ListViewAPI.HDM_SETITEM, newColumn, ref hdItem);
				_previouslySortedColumn = newSortColumn;
			}
		}

		private void ApplyFilter()
		{
			// Clear existing items
			if (this.Items.Count > 0)
				this.Items.Clear();
			this.ListViewItemSorter = null;

			if (this.Filter.Length == 0)
			{
				// Clear the Filter Flag
				this.IsFiltered = false;

				// Load all items
				this.Items.AddRange(this.dataListItems.ToArray());
			}
			else
			{
				// Set the Filtered Flag
				this.IsFiltered = true;

				// Only if we have items
				if (this.dataListItems.Count > 0)
				{
					// Query for the desired rows
					int i = 0;
					List<DataListItem> items = RunQuery(this.dataListItems, Filter, i);
					for (i++; i < this.Columns.Count; i++)
						items.AddRange(RunQuery(this.dataListItems, Filter, i));

					// Add new filtered list
					this.Items.AddRange(items.Distinct().ToArray());
				}
			}

			// Update Sort
			if (this.AllowSort && !this.IsGrouped && this.IsSorted)
				this.Sort(this.ArrangedBy);

			// Update Group
			else if (this.AllowShowGroups && !this.IsSorted && this.IsGrouped)
				this.Group(this.ArrangedBy);

			// Update ColorAlternateLines
			else if (this.UseAlternatingBackColors && this.View == View.Details)
				this.ColorAlternateLines();

		}

		private static List<DataListItem> RunQuery(IEnumerable<DataListItem> items, string filter, int columnIndex)
		{
			var filteredItems =
				from i in items
				let text = i.SubItems[columnIndex].Text.ToLower()
				where text.IndexOf(filter) > -1
				select i;
			return filteredItems.ToList();
		}

		private void CalculateReasonableTileSize()
		{
			if (this.Columns.Count < 1)
				return;

			int imageHeight = (this.LargeImageList == null ? 16 : this.LargeImageList.ImageSize.Height);
			int dataHeight = (this.Font.Height + 1) * this.tileViewColumns.Count;
			int tileWidth = (this.TileSize.Width == 0 ? 150 : this.TileSize.Width);
			int tileHeight = Math.Max(this.TileSize.Height, Math.Max(imageHeight, dataHeight));
			this.TileSize = new Size(tileWidth, tileHeight);
		}

		#endregion

		#region ISupportInitialize Members

		void ISupportInitialize.BeginInit()
		{
			this.Initializing = true;
		}

		void ISupportInitialize.EndInit()
		{
			this.InitializeConfiguration();
			this.Initializing = false;
		}

		#endregion

		#endregion

		#region Design Time
		/// <summary>
		/// This class works in conjunction with the DataListColumns property to allow DataListColumns
		/// to be added to the DataListView.
		/// </summary>
		internal class DataListColumnCollectionEditor : System.ComponentModel.Design.CollectionEditor
		{
			public DataListColumnCollectionEditor(Type t)
				: base(t)
			{
			}

			protected override Type CreateCollectionItemType()
			{
				return typeof(DataListColumn);
			}
		}
		#endregion

		#region Nested Types

		#region ViewManager
		/// <summary>
		/// A DataListView ViewManager
		/// </summary>
		class ViewManager
		{
			#region Fields
			readonly object lockList = new object();
			Dictionary<string, ViewSettings> settings;
			string managerName;
			DataListView owner;
			#endregion

			#region Constructor
			/// <summary>
			/// ctor
			/// </summary>
			/// <param name="Owner">The owning DataListView control</param>
			internal ViewManager(DataListView Owner)
			{
				settings = new Dictionary<string, ViewSettings>();
				owner = Owner;
				managerName = owner.Name;
				if (string.IsNullOrEmpty(managerName))
					managerName = "DataListView" + owner.GetHashCode().ToString("x");
				managerName = System.Reflection.Assembly.GetEntryAssembly().GetName().Name + "." + managerName;
				try
				{
					string[] names = Application.UserAppDataRegistry.GetValueNames();
					foreach (string name in names)
					{
						if (name.StartsWith(managerName))
						{
							string val = Application.UserAppDataRegistry.GetValue(name).ToString();
							ViewSettings v = ViewSettings.FromString(val);
							v.IsPersisted = true;
							settings.Add(v.Name, v);
						}
					}
				}
				catch { }
			}
			#endregion

			#region Methods
			/// <summary>
			/// Fetch a ViewSetting from the Manager
			/// </summary>
			/// <param name="viewName">The viewName of the View to get</param>
			/// <returns>The requested view if found, otherwise null</returns>
			public ViewSettings Get(string viewName)
			{
				lock (lockList)
				{
					if (null == settings)
						return null;

					if (settings.Count == 0)
						return null;

					ViewSettings value;
					settings.TryGetValue(viewName, out value);
					return value;
				}
			}
			/// <summary>
			/// Adds a ViewSetting to the Manager
			/// </summary>
			/// <param name="View">The ViewSettings to add</param>
			public void Add(ViewSettings View)
			{
				lock (lockList)
				{
					if (View.ShouldPersist)
					{
						View.IsPersisted = true;
						string keyName = managerName + "_" + View.Name.Replace(" ", "");
						Application.UserAppDataRegistry.SetValue(keyName, View.ToString());
					}
					settings.Add(View.Name, View);
				}
			}
			/// <summary>
			/// Locates and removes the requested ViewSettings from the viewMgr
			/// </summary>
			/// <param name="viewName">The viewName of the ViewSettings to remove</param>
			public void Remove(string viewName)
			{
				lock (lockList)
				{
					if (this.Contains(viewName))
						settings.Remove(viewName);

					string[] names = Application.UserAppDataRegistry.GetValueNames();
					foreach (string name in names)
					{
						if (name.StartsWith(managerName))
						{
							string val = Application.UserAppDataRegistry.GetValue(name).ToString();
							ViewSettings v = ViewSettings.FromString(val);
							if (v.Name == viewName)
								Application.UserAppDataRegistry.DeleteValue(name);
						}
					}
				}
			}
			/// <summary>
			/// Indicates if a specific ViewSetting instance exists in the Manager
			/// </summary>
			/// <param name="viewName">The name of the view to evaluate</param>
			/// <returns>true if found, otherwise false</returns>
			public bool Contains(string viewName)
			{
				lock (lockList)
				{
					if (null == settings)
						return false;

					if (settings.Count == 0)
						return false;

					return settings.ContainsKey(viewName);
				}
			}
			/// <summary>
			/// Persist a ViewSettings instance
			/// </summary>
			/// <param name="v">The ViewSettings to persist</param>
			public void Persist(ViewSettings v)
			{
				v.IsPersisted = true;
				string keyName = managerName + "_" + v.Name.Replace(" ", "");
				Application.UserAppDataRegistry.SetValue(keyName, v.ToString());
			}
			private bool IsPersisted(string viewName)
			{
				string[] names = Application.UserAppDataRegistry.GetValueNames();
				foreach (string name in names)
				{
					if (name.StartsWith(managerName))
					{
						string val = Application.UserAppDataRegistry.GetValue(name).ToString();
						ViewSettings v = ViewSettings.FromString(val);
						if (v.Name == viewName)
							return true;
					}
				}
				return false;
			}
			#endregion
		}
		#endregion

		#region Comparers

		#region ListViewGroupComparer
		/// <summary>
		/// This comparer sort list view groups.
		/// It does this on the basis of the values in the Tags, if we can figure out how to compare
		/// objects of that type. Failing that, it uses a case insensitive compare on the group header.
		/// </summary>
		internal class ListViewGroupComparer : IComparer<ListViewGroup>
		{
			public ListViewGroupComparer(SortOrder order)
			{
				this.sortOrder = order;
				this.isComparerAlreadyCalculated = false;
			}

			public int Compare(ListViewGroup x, ListViewGroup y)
			{
				if (!this.isComparerAlreadyCalculated)
				{
					this.isComparerAlreadyCalculated = true;
					compareTags = true;
					this.comparer = ComparerManager.GetComparerFor(x.Tag);
					if (this.comparer == null)
					{
						this.comparer = ComparerManager.GetComparerFor(typeof(string));
						compareTags = false;
					}
				}

				int result;

				if (compareTags)
					result = this.comparer.Compare(x.Tag, y.Tag); // Tag is the group key
				else
					result = this.comparer.Compare(x.Header, y.Header);

				if (this.sortOrder == SortOrder.Descending)
					result = 0 - result;

				return result;
			}

			private SortOrder sortOrder;
			private IComparer comparer;
			private bool isComparerAlreadyCalculated;
			private bool compareTags;
		}
		#endregion

		#region DataListItemComparer
		/// <summary>
		/// Allow manual sorting of list items from a List&lt;DataListItem&gt;
		/// </summary>
		internal class DataListItemComparer : IComparer<DataListItem>
		{
			/// <summary>
			/// Allow manual sorting of list items from a List&lt;DataListItem&gt;
			/// </summary>
			/// <param name="order">The Sort Order to apply</param>
			/// <param name="column">The column to sort on</param>
			public DataListItemComparer(SortOrder order, DataListColumn column)
			{
				this.sortOrder = order;
				this.isComparerAlreadyCalculated = false;
				this.column = column;
			}


			public int Compare(DataListItem x, DataListItem y)
			{
				int result = 0;
				object x1 = this.column.GetValue(x.Data);
				object y1 = this.column.GetValue(y.Data);

				// Handle nulls. Null values come last
				if (x1 == null || y1 == null)
					if (x1 == null && y1 == null)
						result = 0;
					else
						result = (x1 == null) ? -1 : 1;
				else if (x1 == DBNull.Value || y1 == DBNull.Value)
					if (x1 == DBNull.Value && y1 == DBNull.Value)
						result = 0;
					else
						result = (x1 == DBNull.Value) ? -1 : 1;
				else if (x1.GetType() == typeof(string) && y1.GetType() == typeof(string))
				{
					string strX1 = (string)x1;
					string strY1 = (string)y1;

					if (strX1 == string.Empty || strY1 == string.Empty)
						if (strX1 == string.Empty && strY1 == string.Empty)
							result = 0;
						else
							result = (strX1 == string.Empty) ? -1 : 1;
					else
						result = this.CompareValues(x1, y1);
				}
				else
					result = this.CompareValues(x1, y1);

				if (this.sortOrder == SortOrder.Descending)
					result = 0 - result;

				return result;

			}

			public int CompareValues(object x, object y)
			{
				if (!this.isComparerAlreadyCalculated)
				{
					this.isComparerAlreadyCalculated = true;
					this.comparer = ComparerManager.GetComparerFor(x);
					if (this.comparer == null)
						this.comparer = ComparerManager.GetComparerFor(typeof(string));
				}

				if (this.comparer == null)
					return 0;

				return this.comparer.Compare(x, y);
			}

			private DataListColumn column;
			private SortOrder sortOrder;
			private IComparer comparer;
			private bool isComparerAlreadyCalculated;
		}
		#endregion

		#region DataListColumnComparer
		/// <summary>
		/// DataListColumnComparer is the workhorse for all comparison between two values of a particular column.
		/// If the column has a specific comparer, use that to compare the values. Otherwise, do
		/// a case insensitive string compare of the string representations of the values.
		/// </summary>
		internal class DataListColumnComparer : IComparer
		{
			public DataListColumnComparer(DataListColumn col, SortOrder order)
			{
				this.column = col;
				this.sortOrder = order;
				this.isComparerAlreadyCalculated = false;
			}

			public int Compare(object x, object y)
			{
				int result = 0;
				object x1 = this.column.GetValue(((DataListItem)x).Data);
				object y1 = this.column.GetValue(((DataListItem)y).Data);

				// Handle nulls. Null values come last
				if (x1 == null || y1 == null)
					if (x1 == null && y1 == null)
						result = 0;
					else
						result = (x1 == null) ? -1 : 1;
				else if (x1 == DBNull.Value || y1 == DBNull.Value)
					if (x1 == DBNull.Value && y1 == DBNull.Value)
						result = 0;
					else
						result = (x1 == DBNull.Value) ? -1 : 1;
				else if (x1.GetType() == typeof(string) && y1.GetType() == typeof(string))
				{
					string strX1 = (string)x1;
					string strY1 = (string)y1;

					if (strX1 == string.Empty || strY1 == string.Empty)
						if (strX1 == string.Empty && strY1 == string.Empty)
							result = 0;
						else
							result = (strX1 == string.Empty) ? -1 : 1;
					else
						result = this.CompareValues(x1, y1);
				}
				else
					result = this.CompareValues(x1, y1);

				if (this.sortOrder == SortOrder.Descending)
					result = 0 - result;

				return result;
			}

			public int CompareValues(object x, object y)
			{
				if (!this.isComparerAlreadyCalculated)
				{
					this.isComparerAlreadyCalculated = true;
					this.comparer = ComparerManager.GetComparerFor(x);
					if (this.comparer == null)
						this.comparer = ComparerManager.GetComparerFor(typeof(string));
				}

				if (this.comparer == null)
					return 0;

				return this.comparer.Compare(x, y);
			}

			private DataListColumn column;
			private SortOrder sortOrder;
			private IComparer comparer;
			private bool isComparerAlreadyCalculated;
		}
		#endregion

		#endregion

		#endregion
	}
	#endregion
}